#include "../include/Market.h"
#include <string>
Market::Market()
{
}

Market::Market(Bank& newBank, std::vector<Events*>* eevents, bool agentOn, std::string confFile, std::string eventFile, int id1, int id2, vector<double> eValue, double firstYitra, double sayaBuy): users(std::vector<User*>()), events(*eevents), buys(vector<BuyEvent*>()), sells(vector<SellEvent*>()), energyValue(eValue), energyAmount(vector<int>(7,0)), lastDealDate(new Poco::DateTime()), CMR(confFile, eventFile, id1, id2), AgentOn(agentOn), sayaEvents(vector<Events*>())
{
	int i=0;
	for (; i<6; i++)
		users.push_back(new User(ass2::eUserId(ass2::eUser(i)), newBank, firstYitra));
	for (i++; i<11; i++)
			users.push_back(new User(ass2::eUserId(ass2::eUser(i)), newBank, firstYitra));
	if (AgentOn)
		users.push_back(new Agent(ass2::eUserId(ass2::eUser(6)), newBank, sayaBuy, firstYitra));


	cout<<"before sort"<<endl;
	std::sort(events.begin(), events.end(), Events::after);
	cout<<"after sort"<<endl;

}
Market::Market(std::vector<User*> tUsers, std::vector<BuyEvent*> tBuyEvents, std::vector<SellEvent*> tSellEvents): users(tUsers), events(std::vector<Events*>()), buys(tBuyEvents), sells(tSellEvents), energyValue(std::vector<double>(7,100)), energyAmount(std::vector<int>(7,0)), lastDealDate(new Poco::DateTime()), CMR("config.ini", "Events.ini" , 21911425, 36938173), AgentOn(false), sayaEvents(std::vector<Events*>())
{

}

Market::~Market()
{
	while (users.size() !=0){
		delete (users.back());
		users.pop_back();
	}
	delete lastDealDate;

	while (sayaEvents.size() !=0){
		delete (sayaEvents.back());
		sayaEvents.pop_back();
	}

}


void Market::startSim(){
	while (!events.empty()) { //while there are more events
		cout<<"create"<<endl;
		executeCreateConsume();
		cout<<"before read buy"<<endl;
		readBuysSells();
		cout<<"after read"<<endl;
		executeDeals();
		recalculate_the_state_of_the_system();
		if (AgentOn){
			generate_Saya_events();
		}
		execute_the_report_events();
		endOfDay();
	}

}

User* Market::getUser (int id){

	int j=0;
	for ( int i =0; i<users.size();  i++)
		if (users.at(i)->getId() == id){
			j =i;
		}
	return (users.at(j));
}

void Market::executeCreateConsume(){

	while (events.size() !=0 && events.back()->getType() < Events::buy){
		while (events.size() !=0 && events.back()->getType() == Events::create){
			int userId = (dynamic_cast<CreateEvent*>(events.back()))->_userId;
			ass2::eEnergy energyType = (dynamic_cast<CreateEvent*>(events.back()))->_energyType;
			int amount = (dynamic_cast<CreateEvent*>(events.back()))->numOfUnits;

			User* user = getUser(userId);
			user->createEnergy(energyType, amount);
			energyAmount.at(energyType) += amount;
			events.pop_back();

		}
		if (events.size() !=0 && events.back()->getType() == Events::consume){
			int userId = (dynamic_cast<ConsumeEvent*>(events.back()))->_userId;
			ass2::eEnergy energyType = (dynamic_cast<ConsumeEvent*>(events.back()))->_energyType;
			int amount = (dynamic_cast<ConsumeEvent*>(events.back()))->numOfUnits;

			User* user = getUser(userId);
			if (user->getResource(energyType).getAmount() > amount) {
				user->consumeEnergy(energyType, amount);
				energyAmount.at(energyType) -= amount;
			}
			events.pop_back();
		}
	}
}


void Market::readBuysSells(){


	while (events.size() !=0 && events.back()->getType() == Events::buy){

		buys.push_back(dynamic_cast<BuyEvent*>(events.back()));
		events.pop_back();
	}
	while (events.size() !=0 && events.back()->getType() == Events::sell){
		sells.push_back(dynamic_cast<SellEvent*>(events.back()));
		events.pop_back();
	}
	std::sort(buys.begin(), buys.end(), Events::after);
	std::sort(sells.begin(), sells.end(), Events::after);
}


void Market::executeDeals(){
	while (buys.size() > 0 & sells.size() > 0) {
		if (buys.back()->_energyType < sells.back()->_energyType) {
			buys.pop_back();
			continue;
		}
		if (buys.back()->_energyType > sells.back()->_energyType) {
			sells.pop_back();
			continue;
		}
		if (buys.back()->date < sells.back()->date) {
			buys.pop_back();
			continue;
		}
		if (buys.back()->date > sells.back()->date) {
			sells.pop_back();
			continue;
		}


		BuyEvent* buy = (buys.back());
		SellEvent* sell= (sells.back());

		int buyerId = buy->_userId;
		int sellerId = sell->_userId;
		int amountExchanged=0;
		amountExchanged = std::min(buy->getAmount(), sell->getAmount());

		ass2::eEnergy type = buy->_energyType;
		double price = (((buy->_price) + (sell->_price)) / 2);

		User *buyer = getUser(buyerId);
		User *seller = getUser(sellerId);


		bool dealDone;
		dealDone = deal(buyer, seller);

		if (dealDone){
			cout<<"((("<<amountExchanged<<"*"<<price<<") + (("<<energyAmount.at(type)<<" -"<<amountExchanged<<")*"<<energyValue.at(type)<<")) / "<<energyAmount.at(type)<<")"<<endl;
			double newValue = (((amountExchanged*price) + ((energyAmount.at(type) -amountExchanged)*energyValue.at(type))) / energyAmount.at(type));
			if (AgentOn && newValue < energyValue.at(type)) {
				dynamic_cast<Agent*>(users.back())->countDown(type);
			}

			if (AgentOn && newValue > energyValue.at(type))
				dynamic_cast<Agent*>(users.back())->countUp(type);

			std::ostringstream strStream ;
			strStream << "Energy "<<ass2::energyToString(type)<<" value changes from "<<energyValue.at(type)<<" to "<<newValue<<".";
			EnergyMarketLogger::Instance().Log(strStream , Poco::Message::PRIO_WARNING);

			energyValue.at(type) = newValue;
			*lastDealDate = Poco::DateTime(*(buy->date));///////////////////////
			if ((buy->getAmount()-amountExchanged) > 0 )
				buy->setAmount((buy->getAmount()-amountExchanged));
			else
				buys.pop_back();
			if ((sell->getAmount()-amountExchanged) > 0 )
				sell->setAmount(sell->getAmount()-amountExchanged);
			else{
				sells.pop_back();
			}
		}
	}
}

bool Market::deal(User* buyer, User* seller){
	bool done = false;

	BuyEvent* buy = dynamic_cast<BuyEvent*>(buys.back());
	SellEvent* sell= dynamic_cast<SellEvent*>(sells.back());
	int amountExchanged=0;
	amountExchanged = std::min(buy->getAmount(), sell->getAmount());
	double price = (((buy->_price) + (sell->_price)) / 2);

	ass2::eEnergy type = buy->_energyType;

	if (buyer->getYitra() < ((amountExchanged*price)*100.5/100)) {
		std::ostringstream strStream;
		strStream <<"User "<<ass2::eUserName(ass2::Id2eUser(buyer->getId()))<<" doesn't have enough money.";
		EnergyMarketLogger::Instance().Log(strStream , Poco::Message::PRIO_ERROR);
		buys.pop_back();
	}
	if (seller->getResource(type).getAmount() < amountExchanged) {
		std::ostringstream strStream;
 		strStream <<"User "<<ass2::eUserName(ass2::Id2eUser(seller->getId()))<<" doesn't have enough "<<ass2::energyToString(type)<<" units.";
 		EnergyMarketLogger::Instance().Log(strStream , Poco::Message::PRIO_ERROR);
		sells.pop_back();
	}

	if (buyer->getYitra() > ((amountExchanged*price)*100.5/100)
		& (seller->getResource(type).getAmount() > amountExchanged)){
			buyer->buyEnergy(type, amountExchanged, price);
			seller->sellEnergy(type, amountExchanged, price);
			done = true;

			std::ostringstream strStream ;
			strStream <<"user "<<ass2::eUserName(ass2::Id2eUser(seller->getId()))<<" sold to user "<<ass2::eUserName(ass2::Id2eUser(buyer->getId()))
											<<" a "<<amountExchanged<<" of "<<ass2::energyToString(type)<<" for a "<<price<<".";
			EnergyMarketLogger::Instance().Log(strStream , Poco::Message::PRIO_NOTICE);
	}

	return done;

}
void Market::recalculate_the_state_of_the_system(){
	if (AgentOn){
		dynamic_cast<Agent*>(users.back())->setDate(lastDealDate);
		dynamic_cast<Agent*>(users.back())->setNewValues(energyValue);
	}
	while (buys.size() != 0){
		buys.pop_back();
	}

	while (sells.size() != 0){
		sells.pop_back();
	}
}

void Market::endOfDay(){
	for (int i=0; i<users.size(); i++)
		users.at(i)->endOfDay();
}

void Market::generate_Saya_events(){
	std::vector<BuyEvent*> buyingEvents = dynamic_cast<Agent*>(users.back())->generateBuyEvents();
	std::vector<SellEvent*> sellingEvents = dynamic_cast<Agent*>(users.back())->generateSellEvents();


	while(buyingEvents.size() != 0){
		sayaEvents.push_back(buyingEvents.back());
		buys.push_back(buyingEvents.back());
		buyingEvents.pop_back();
	}

	while(sellingEvents.size() != 0){
		sayaEvents.push_back(sellingEvents.back());
		sells.push_back(sellingEvents.back());
		sellingEvents.pop_back();
	}

}
void Market::execute_the_report_events(){


	while (events.size() !=0 && events.back()->getType() >= Events::userReport){
		std::ostringstream strStream ;

		if (events.back()->getType() == Events::userReport){
			execute(dynamic_cast<UserReport*>(events.back()));
			strStream << "User";
		}

		if (events.back()->getType() == Events::energyReport){
			execute(dynamic_cast<EnergyReport*>(events.back()));
			strStream << "Energy";
		}

		if (events.back()->getType() == Events::profitReport){
			execute(dynamic_cast<ProfitReport*>(events.back()));
			strStream << "Profit";
		}

		if (events.back()->getType() == Events::activityReport){
			execute(dynamic_cast<ActivityReport*>(events.back()));
			strStream << "Activity";
		}

		if (events.back()->getType() == Events::accountReport){
			execute(dynamic_cast<AccountReport*>(events.back()));
			strStream << "Account";
		}

		strStream  << " report was generated.";
		EnergyMarketLogger::Instance().Log(strStream , Poco::Message::PRIO_NOTICE);
		events.pop_back();

	}
}

void Market::execute (UserReport *report){

	User* user = getUser(report->_userId);
	ass2::unitContainer UC;
	UC.coal = user->getResource(ass2::coal).getAmount();
	UC.gas = user->getResource(ass2::gas).getAmount();
	UC.hug = user->getResource(ass2::hug).getAmount();
	UC.oil = user->getResource(ass2::oil).getAmount();
	UC.smile = user->getResource(ass2::smile).getAmount();
	UC.sun = user->getResource(ass2::sun).getAmount();
	UC.wind = user->getResource(ass2::wind).getAmount();
	std::string userName = ass2::eUserName(Id2eUser(report->_userId));


	CMR.CMarketReport::reportUser(report->_userId, userName, UC, *report->date);
}
void Market::execute (AccountReport *report){

	User* user = getUser(report->_userId);
	std::string userName = ass2::eUserName(Id2eUser(report->_userId));

	CMR.CMarketReport::reportAccount(report->_userId, userName, user->getYitra(), *report->date);
}
void Market::execute (EnergyReport *report){



	ass2::valueContainer VC;
	VC.coal = energyValue.at(ass2::coal);
    VC.gas = energyValue.at(ass2::gas);
	VC.hug = energyValue.at(ass2::hug);
	VC.oil = energyValue.at(ass2::oil);
	VC.smile = energyValue.at(ass2::smile);
	VC.sun = energyValue.at(ass2::sun);
	VC.wind = energyValue.at(ass2::wind);

	CMR.CMarketReport::reportEnergy(&VC, *report->date);
}
void Market::execute (ProfitReport *report){

	User* user = getUser(report->_userId);
	std::string userName = ass2::eUserName(Id2eUser(report->_userId));

	CMR.CMarketReport::reportProfit(report->_userId, userName, user->getProfit(), *report->date);
}
void Market::execute (ActivityReport *report){

	User* user = getUser(report->_userId);

		ass2::unitContainer UCcreated;
		UCcreated.coal = user->getResource(ass2::coal).getCreated();
		UCcreated.gas = user->getResource(ass2::gas).getCreated();
		UCcreated.hug = user->getResource(ass2::hug).getCreated();
		UCcreated.oil = user->getResource(ass2::oil).getCreated();
		UCcreated.smile = user->getResource(ass2::smile).getCreated();
		UCcreated.sun = user->getResource(ass2::sun).getCreated();
		UCcreated.wind = user->getResource(ass2::wind).getCreated();

		ass2::unitContainer UCconsumed;
		UCconsumed.coal = user->getResource(ass2::coal).getConsumed();
		UCconsumed.gas = user->getResource(ass2::gas).getConsumed();
		UCconsumed.hug = user->getResource(ass2::hug).getConsumed();
		UCconsumed.oil = user->getResource(ass2::oil).getConsumed();
		UCconsumed.smile = user->getResource(ass2::smile).getConsumed();
		UCconsumed.sun = user->getResource(ass2::sun).getConsumed();
		UCconsumed.wind = user->getResource(ass2::wind).getConsumed();

		std::string userName = ass2::eUserName(Id2eUser(report->_userId));

	CMR.CMarketReport::reportActivity(userName, UCcreated, UCconsumed, *report->date);
}





